﻿namespace ImapComponent.Net.Mail.Mime
{
    using System.Collections.Specialized;
    using System.IO;
    using System.Net.Mime;
    using System.Text;
    using System.Text.RegularExpressions;

    using ImapComponent.Net.Mail.Utils;

    /// <summary>
    /// Represents the mime part.
    /// </summary>
    public abstract class MimePart
    {
        #region Fields

        private readonly string contentId;
        private readonly byte[] contentData;
        private readonly ContentType contentType;
        private readonly TransferEncoding transferEncoding;
        private readonly MimePart parent;
        protected NameValueCollection headers;

        #endregion // Fields

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the MimePart class.
        /// </summary>
        /// <param name="parent">The parent of this mime part.</param>
        /// <param name="contentData">A data content.</param>
        /// <param name="contentType">A content type.</param>
        /// <param name="contentId">A content id.</param>
        /// <param name="transferEncoding">A transfer encoding.</param>
        protected MimePart(MimePart parent, byte[] contentData, ContentType contentType, string contentId, TransferEncoding transferEncoding)
        {
            this.parent = parent;
            this.contentData = contentData;
            this.contentType = contentType;
            this.contentId = contentId;
            this.transferEncoding = transferEncoding;

            this.headers = new NameValueCollection();
        }

        #endregion // Constructors

        #region Properties

        /// <summary>
        /// Gets the parent of the mime entity.
        /// </summary>
        public MimePart Parent
        {
            get
            {
                return this.parent;
            }
        }

        /// <summary>
        /// Gets the content id.
        /// </summary>
        public string ContentId
        {
            get
            {
                return this.contentId;
            }
        }

        /// <summary>
        /// Gets the data content.
        /// </summary>
        public byte[] ContentData
        {
            get
            {
                return this.contentData;
            }
        }

        /// <summary>
        /// Gets the content type.
        /// </summary>
        public ContentType ContentType
        {
            get
            {
                return this.contentType;
            }
        }

        /// <summary>
        /// Gets the transfer encoding.
        /// </summary>
        public TransferEncoding TransferEncoding
        {
            get
            {
                return this.transferEncoding;
            }
        }

        #endregion // Properties

        #region Methods

        /// <summary>
        /// Parses the mime part string to the mime part.
        /// </summary>
        /// <param name="parent">The parent of the mime part.</param>
        /// <param name="mimePartString">A mime part string.</param>
        /// <returns>The parsed mime part.</returns>
        internal static MimePart Parse(MimePart parent, string mimePartString)
        {
            MimePart resultPart;

            NameValueCollection headers = new NameValueCollection();
            string mimeString = ParseHeaders(mimePartString, headers);

            string contentId = headers.AllKeys.Contains("content-id") ? headers.GetValues("content-id")[0] : string.Empty;
            string transferEncodingString = headers.AllKeys.Contains("content-transfer-encoding") ? headers.GetValues("content-transfer-encoding")[0] : string.Empty;

            TransferEncoding transferEncoding;

            switch (transferEncodingString.ToLower())
            {
                case "":
                    transferEncoding = TransferEncoding.Unknown;
                    break;
                case "base64":
                    transferEncoding = TransferEncoding.Base64;
                    break;
                case "quoted-printable":
                    transferEncoding = TransferEncoding.QuotedPrintable;
                    break;
                case "7bit":
                    transferEncoding = TransferEncoding.SevenBit;
                    break;
                default:
                    transferEncoding = TransferEncoding.Unknown;
                    break;
            }

            string contentTypeString = headers.AllKeys.Contains("content-type") ? Regex.Replace(headers.GetValues("content-type")[0], "\r\n", string.Empty) : "text/plain;charset=\"utf-8\"";
            ContentType contentType = Helper.GetContentType(contentTypeString);

            /// multipart
            if (contentType.MediaType.ToLower().StartsWith("multipart"))
            {
                if (string.IsNullOrEmpty(contentType.Boundary))
                {
                    throw new InvalidDataException("Not a mime message.");
                }
                else
                {
                    StringReader stringReader = new StringReader(mimeString);
                    StringBuilder stringBuilder = new StringBuilder();

                    string line = stringReader.ReadLine();

                    while (line != null)
                    {
                        if (line.ToLower().StartsWith("--" + contentType.Boundary))
                        {
                            break;
                        }

                        stringBuilder.AppendLine(line);
                        line = stringReader.ReadLine();
                    }

                    string bodyString = stringBuilder.Length > 2 ? stringBuilder.ToString(0, stringBuilder.Length - 2) : string.Empty;
                    byte[] body = StringEncoder.DecodeData(transferEncoding, Encoding.ASCII.GetBytes(bodyString));

                    resultPart = new MimeMultipart(parent, body, contentType, contentId, transferEncoding);

                    if (string.IsNullOrEmpty(line))
                    {
                        throw new InvalidDataException("Not a mime message.");
                    }

                    while (true)
                    {
                        if (line.ToLower().StartsWith("--" + contentType.Boundary + "--"))
                        {
                            break;
                        }

                        line = stringReader.ReadLine();
                        stringBuilder = new StringBuilder();

                        while (line != null)
                        {
                            if (line.ToLower().StartsWith("--" + contentType.Boundary))
                            {
                                break;
                            }

                            stringBuilder.AppendLine(line);
                            line = stringReader.ReadLine();
                        }

                        string mimePartSubstring = stringBuilder.Length > 2 ? stringBuilder.ToString(0, stringBuilder.Length - 2) : string.Empty;

                        try
                        {
                            ((MimeMultipart)resultPart).Children.Add(MimePart.Parse(resultPart, mimePartSubstring));
                        }
                        catch
                        {
                            /// skip invalid mime part
                        }

                        resultPart.headers = headers;
                        return resultPart;
                    }

                    throw new InvalidDataException("Not a mime message.");
                }
            }
            /// mime attachment
            else if (headers.AllKeys.Contains("content-disposition"))
            {
                byte[] body = StringEncoder.DecodeData(transferEncoding, Encoding.ASCII.GetBytes(mimeString));
                string contentDispositionString = Regex.Replace(headers.GetValues("content-disposition")[0], "\r\n", string.Empty);
                ContentDisposition contentDisposition = Helper.GetContentDisposition(contentDispositionString);

                resultPart = new MimeAttachment(parent, body, contentType, contentId, transferEncoding, contentType.Name, contentDisposition);
                resultPart.headers = headers;

                return resultPart;
            }
            /// single part
            else
            {
                byte[] body = StringEncoder.DecodeData(transferEncoding, Encoding.ASCII.GetBytes(mimeString));

                resultPart = new MimeSinglepart(parent, body, contentType, contentId, transferEncoding);
                resultPart.headers = headers;

                return resultPart;
            }
        }

        /// <summary>
        /// Parses headers of the mime part.
        /// </summary>
        /// <param name="mimePartString">A mime part string.</param>
        /// <param name="headers">A header collection.</param>
        /// <returns>The mime body part.</returns>
        private static string ParseHeaders(string mimePartString, NameValueCollection headers)
        {
            StringReader stringReader = new StringReader(mimePartString);
            string line = stringReader.ReadLine();

            while (line != null)
            {
                if (line == string.Empty)
                {
                    break;
                }

                string headerLine = line;
                line = stringReader.ReadLine();

                while (line != null && (line.StartsWith("\t") || line.StartsWith(" ")))
                {
                    headerLine += line;
                    line = stringReader.ReadLine();
                }

                string[] headerParsedLine = headerLine.Split(new char[] { ':' }, 2);

                if (headerParsedLine.Length == 2)
                {
                    headers.Add(headerParsedLine[0].ToLower(), StringEncoder.DecodeHeaderValue(headerParsedLine[1].Trim()).ToLower());
                }
            }

            return stringReader.ReadToEnd();
        }

        #endregion // Methods
    }
}
